Skip to content

[中文版本](/posts/c教程/zh-CN/06-编译与构建/053-Conditional Compilation (if, ifdef, ifndef))

Conditional compilation lets you control which code gets compiled, not at runtime, but at compile time. This is how C programs adapt to different operating systems, architectures, or build configurations without changing source files manually.

Think of it as logic for the compiler’s eyes only.

Step 1. Why Conditional Compilation Exists ​

Large C programs often need to handle differences such as:

  • Platform (Windows, Linux, macOS, embedded)
  • Compiler (gcc, clang, MSVC)
  • Debug vs release builds
  • Optional features or experimental modules

Instead of maintaining multiple versions of the same file, you can use conditional directives to selectively include or exclude code.

Step 2. The Core Directives ​

DirectivePurpose
#if Compile code if expression is true
#ifdef Compile if macro is defined
#ifndef Compile if macro is not defined
#elseAlternate block
#elif Else-if for preprocessor
#endifMarks the end of a conditional block

These work only during preprocessing, before compilation starts.

Tiny Code: Platform-Specific Compilation ​

#include <stdio.h>
int main(void) {
#ifdef _WIN32
    printf("Running on Windows\n");
#elif __linux__
    printf("Running on Linux\n");
#elif __APPLE__
    printf("Running on macOS\n");
#else
    printf("Unknown platform\n");
#endif
    return 0;
}

Compile and run on your system. The output will depend on which predefined macros your compiler sets automatically.

Step 3. Enabling and Disabling Features ​

You can define flags at compile time with-D:

gcc -DDEBUG log.c -o log

In your code:

#ifdef DEBUG
    printf("Debug mode: extra checks enabled\n");
#endif

No recompilation needed to switch, just re-rungcc with or without-DDEBUG.

You can also assign values:

gcc -DVERSION=2 main.c -o main

Then:

#if VERSION >= 2
    printf("New feature enabled!\n");
#endif

Step 4. Guarding Code with #ifndef ​

This is one of the most common idioms in C headers:

#ifndef CONFIG_H
#define CONFIG_H
#define MAX_CLIENTS 100
#define TIMEOUT_MS 3000
#endif

It ensures that ifconfig.h is included multiple times, it only gets processed once. Every header in the C standard library uses this pattern.

Step 5. Excluding Experimental Code ​

#define ENABLE_EXPERIMENTAL 0
#if ENABLE_EXPERIMENTAL
void experimental_feature() {
    printf("Running experimental feature\n");
}
#endif

IfENABLE_EXPERIMENTAL is set to0, this code is completely removed before compilation, it doesn’t even exist in the object file.

Tiny Code: Debug Mode Example ​

#include <stdio.h>
#define DEBUG_MODE 1
void compute(int x) {
#if DEBUG_MODE
    printf("[DEBUG] compute() called with x=%d\n", x);
#endif
    printf("Result: %d\n", x * x);
}
int main(void) {
    compute(5);
    return 0;
}

Output whenDEBUG_MODE is 1:

[DEBUG] compute() called with x=5
Result: 25

SetDEBUG_MODE to 0, recompile, and the[DEBUG] message disappears entirely.

Step 6. Using #elif and #else ​

#define OS 2
#if OS == 1
    #define OS_NAME "Windows"
#elif OS == 2
    #define OS_NAME "Linux"
#else
    #define OS_NAME "Unknown"
#endif
int main(void) {
    printf("OS: %s\n", OS_NAME);
    return 0;
}

Output:

OS: Linux

Step 7. Combining with Logical Operators ​

You can use&&,||, and! in preprocessor conditions.

#if defined(DEBUG) && !defined(RELEASE)
    printf("Debug build only\n");
#endif

You can even use numeric comparisons:

#if VERSION >= 3
    printf("Version 3+ detected\n");
#endif

Step 8. Forcing Compilation Errors ​

Sometimes you want to stop compilation if a required macro is missing:

#ifndef API_KEY
#error "API_KEY not defined! Please compile with -DAPI_KEY=your_key"
#endif

This is useful for configuration validation at build time.

Step 9. Compiler-Specific Macros ​

Compilers automatically define macros to identify themselves and the environment.

MacroMeaning
__GNUC__Defined by GCC
__clang__Defined by Clang
_MSC_VERDefined by MSVC
__x86_64__64-bit architecture
__arm__,__aarch64__ARM architectures
__STDC__Conforms to ANSI C standard

You can use these to write portable, adaptive code:

#ifdef __clang__
    printf("Compiled with Clang\n");
#elif defined(__GNUC__)
    printf("Compiled with GCC\n");
#endif

Tiny Code: Portable Sleep Function ​

#include <stdio.h>
#ifdef _WIN32
    #include <windows.h>
    #define SLEEP(ms) Sleep(ms)
#else
    #include <unistd.h>
    #define SLEEP(ms) usleep((ms) * 1000)
#endif
int main(void) {
    printf("Waiting...\n");
    SLEEP(1000);
    printf("Done!\n");
    return 0;
}

This compiles cleanly on both Windows and Linux with no code changes.

Why It Matters ​

Conditional compilation makes your C code:

  • Portable, same code runs on multiple systems
  • Configurable, features can be toggled at build time
  • Maintainable, no need for multiple codebases
  • Efficient, excluded code doesn’t even enter the binary

In system software and embedded development, this is indispensable.

Try It Yourself ​

  1. Write a program that prints a different greeting depending on the OS.
  2. Use#if and#error to enforce that only one ofDEBUG orRELEASE can be defined.
  3. Write a header that defines constants for different CPU architectures.
  4. Add a feature flag (ENABLE_LOGGING) that can be turned on/off viagcc -D.
  5. Use#ifdef and#ifndef to create a lightweight build-time configuration system.

In the next section, you’ll take the next step toward clean, maintainable C code by learning about inline functions and header hygiene, modern, safer replacements for many macro patterns.

本站总访问 — 次